package gui.threed;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import labyrinth.Labyrinth;

import com.sun.j3d.utils.geometry.Box;

/**
 * The 3D model of a labyrinth
 * 
 * @author Johannes Kulick
 * @version 0.1
 * @license GNU Lesser Genaral Public License
 */
public class LabyrinthModel extends BranchGroup {
	/**
	 * The size of a labyrinth block
	 */
	public final static float BLOCK_SIZE = 0.4f;

	/**
	 * The Height of the Ground
	 */
	public final static float GROUND_HEIGHT = 0.01f;

	/**
	 * The matrix representation of the labyrinth
	 */
	private int[][] mLabyrinth;

	/**
	 * Calculates a Branch group for a given nxm-Matrix-description of a
	 * labyrinth
	 * 
	 * @param mLabyrinth
	 *            The nxm-Matrix
	 * @return A Branch Group which represents the Labyrinth
	 */
	public LabyrinthModel(Labyrinth labyrinth) {
		mLabyrinth = labyrinth.getMatrix();

		// The matrix gives the lab-size
		float xsize = mLabyrinth.length * BLOCK_SIZE;
		float ysize = mLabyrinth[0].length * BLOCK_SIZE;

		// create ground box and add to group
		//
		// prepare the appearance of the ground (color etc.)
		Appearance groundApp = new Appearance();
		Material groundMat = new Material();
		groundMat.setDiffuseColor(0.75f, 0.75f, 0.75f);
		groundApp.setMaterial(groundMat);

		// create it
		Box ground = new Box(xsize, GROUND_HEIGHT, ysize, groundApp);
		addChild(ground);

		// build walls from matrix
		for (int x = 0; x < mLabyrinth.length; x++) {
			for (int y = 0; y < mLabyrinth[x].length; y++) {
				switch (mLabyrinth[x][y]) {
				case Labyrinth.WALL:
					addChild(makeWall(x, y));
					break;
				case Labyrinth.GOAL:
					addChild(makeColorField(x, y, new Color3f(0, 0.7f, 0)));
					break;
				case Labyrinth.START:
					addChild(makeColorField(x, y, new Color3f(1, 0, 0)));
					break;
				default:
					break;
				}
			}
		}

		// Create a light
		DirectionalLight light = new DirectionalLight(true, new Color3f(0.8f,
				0.8f, 0.8f), new Vector3f(-0.5f, -0.8f, -0.2f));
		light.setInfluencingBounds(new BoundingSphere(new Point3d(0, 0, 0),
						100));
		addChild(light);

	}

	/**
	 * Creates a color field on the ground of the specified x,y coordinate
	 * 
	 * @param x
	 * @param y
	 * @return A Java3D node
	 */
	private Node makeColorField(int x, int y, Color3f color) {
		float xOffset = (mLabyrinth.length - 1) * BLOCK_SIZE;
		float yOffset = (mLabyrinth[0].length - 1) * BLOCK_SIZE;

		// Prepare move
		TransformGroup wall = new TransformGroup();
		Transform3D translate = new Transform3D();
		translate.set(new Vector3d(2 * x * BLOCK_SIZE - xOffset, 0, 2 * y
				* BLOCK_SIZE - yOffset));
		wall.setTransform(translate);

		// prepare the appearance of the walls
		Appearance wallAppearance = new Appearance();
		Material wallMaterial = new Material();
		wallMaterial.setDiffuseColor(color);
		wallAppearance.setMaterial(wallMaterial);

		// Create wall at the right position
		wall.addChild(new Box(BLOCK_SIZE, GROUND_HEIGHT + 1000
				* Float.MIN_VALUE, BLOCK_SIZE, wallAppearance));
		return wall;
	}

	/**
	 * Creates a wall-block on the specified x,y coordinate
	 * 
	 * @param x
	 * @param y
	 * @return A Java3D node
	 */
	private Node makeWall(int x, int y) {
		float xOffset = (mLabyrinth.length - 1) * BLOCK_SIZE;
		float yOffset = (mLabyrinth[0].length - 1) * BLOCK_SIZE;

		// Prepare move
		TransformGroup wall = new TransformGroup();
		Transform3D translate = new Transform3D();
		translate.set(new Vector3d(2 * x * BLOCK_SIZE - xOffset,
				(BLOCK_SIZE + GROUND_HEIGHT), 2 * y * BLOCK_SIZE - yOffset));
		wall.setTransform(translate);

		// prepare the appearance of the walls
		Appearance wallAppearance = new Appearance();
		Material wallMaterial = new Material();
		wallAppearance.setMaterial(wallMaterial);

		// Create wall at the right position
		wall.addChild(new Box(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE,
				wallAppearance));
		return wall;
	}

}
